/*
 * lexer.c
 *
 * Author: Michael Stegeman
 * SID: 12142906
 * Date: 3/31/08
 *
 * Description: Lexer functions to parse input text and find valid "tokens"
 */

#include <string.h>
#include <ctype.h>
#include "lexer.h"

/* 
 * Main lexer function: Takes "line" as input, delimited by delims, 
 * stores valid tokens in linked list and outputs to out, 
 * outputs invalid tokens to err, returns number of errors found 
 */
int lex(FILE *out, FILE *err, char *line, int linenum, char *delims, struct token_list *tl)
{
    int num_errors = 0;
    char ch;
    char *tok;
    union value val_empty;
    val_empty.i = 0;
    enum lexeme type_empty = NONE;

    /* Find first token in line */
    tok = strtok(line, delims);

    /* Main loop - loop until there are no more tokens */
    while(tok != NULL)
    {
        struct token *t, *t_empty1, *t_empty2;
        union value val;

        /* Check for BEG opcode, push onto list, followed by 2 blanks */
        if(strcmp(tok, "BEG") == 0)
        {
            val.op = BEG;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
            t_empty1 = new_token(linenum, val_empty, type_empty);
            push_token(t_empty1, tl);
            t_empty2 = new_token(linenum, val_empty, type_empty);
            push_token(t_empty2, tl);
        }

        /* Check for END opcode, push onto list, followed by 2 blanks */
        else if(strcmp(tok, "END") == 0)
        {
            val.op = END;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
            t_empty1 = new_token(linenum, val_empty, type_empty);
            push_token(t_empty1, tl);
            t_empty2 = new_token(linenum, val_empty, type_empty);
            push_token(t_empty2, tl);
            break;
        }

        /* Check for MOV opcode, push onto list */
        else if(strcmp(tok, "MOV") == 0)
        {
            val.op = MOV;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
        }

        /* Check for CLR opcode, push onto list, followed by an empty token */
        else if(strcmp(tok, "CLR") == 0)
        {
            val.op = CLR;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
            t_empty1 = new_token(linenum, val_empty, type_empty);
            push_token(t_empty1, tl);
        }

        /* Check for CPY opcode, push onto list */
        else if(strcmp(tok, "CPY") == 0)
        {
            val.op = CPY;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
        }

        /* Check for CMP opcode, push onto list */
        else if(strcmp(tok, "CMP") == 0)
        {
            val.op = CMP;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
        }

        /* Check for JZ opcode, push onto list */
        else if(strcmp(tok, "JZ") == 0)
        {
            val.op = JZ;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
        }

        /* Check for JNZ opcode, push onto list */
        else if(strcmp(tok, "JNZ") == 0)
        {
            val.op = JNZ;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
        }

        /* Check for JMP opcode, push onto list, followed by an empty token */
        else if(strcmp(tok, "JMP") == 0)
        {
            val.op = JMP;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
            t_empty1 = new_token(linenum, val_empty, type_empty);
            push_token(t_empty1, tl);
        }

        /* Check for JGT opcode, push onto list */
        else if(strcmp(tok, "JGT") == 0)
        {
            val.op = JGT;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
        }

        /* Check for JLT opcode, push onto list */
        else if(strcmp(tok, "JLT") == 0)
        {
            val.op = JLT;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
        }

        /* Check for ADD opcode, push onto list */
        else if(strcmp(tok, "ADD") == 0)
        {
            val.op = ADD;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
        }

        /* Check for SUB opcode, push onto list */
        else if(strcmp(tok, "SUB") == 0)
        {
            val.op = SUB;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
        }

        /* Check for MUL opcode, push onto list */
        else if(strcmp(tok, "MUL") == 0)
        {
            val.op = MUL;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
        }

        /* Check for DIV opcode, push onto list */
        else if(strcmp(tok, "DIV") == 0)
        {
            val.op = DIV;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
        }

        /* Check for INC opcode, push onto list, followed by an empty token */
        else if(strcmp(tok, "INC") == 0)
        {
            val.op = INC;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
            t_empty1 = new_token(linenum, val_empty, type_empty);
            push_token(t_empty1, tl);
        }

        /* Check for DEC opcode, push onto list, followed by an empty token */
        else if(strcmp(tok, "DEC") == 0)
        {
            val.op = DEC;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
            t_empty1 = new_token(linenum, val_empty, type_empty);
            push_token(t_empty1, tl);
        }

        /* Check for GET opcode, push onto list, followed by an empty token */
        else if(strcmp(tok, "GET") == 0)
        {
            val.op = GET;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
            t_empty1 = new_token(linenum, val_empty, type_empty);
            push_token(t_empty1, tl);
        }

        /* Check for PUT opcode, push onto list, followed by an empty token */
        else if(strcmp(tok, "PUT") == 0)
        {
            val.op = PUT;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
            t_empty1 = new_token(linenum, val_empty, type_empty);
            push_token(t_empty1, tl);
        }

        /* Check for PTC opcode, push onto list, followed by an empty token */
        else if(strcmp(tok, "PTC") == 0)
        {
            val.op = PTC;
            t = new_token(linenum, val, OPCODE);
            push_token(t, tl);
            t_empty1 = new_token(linenum, val_empty, type_empty);
            push_token(t_empty1, tl);
        }

        /* Check for register R0, push onto list */
        else if(strcmp(tok, "R0") == 0)
        {
            val.r = R0;
            t = new_token(linenum, val, REGISTER);
            push_token(t, tl);
        }

        /* Check for register R1, push onto list */
        else if(strcmp(tok, "R1") == 0)
        {
            val.r = R1;
            t = new_token(linenum, val, REGISTER);
            push_token(t, tl);
        }

        /* Check for register R2, push onto list */
        else if(strcmp(tok, "R2") == 0)
        {
            val.r = R2;
            t = new_token(linenum, val, REGISTER);
            push_token(t, tl);
        }

        /* Check for register R3, push onto list */
        else if(strcmp(tok, "R3") == 0)
        {
            val.r = R3;
            t = new_token(linenum, val, REGISTER);
            push_token(t, tl);
        }

        /* Check for register R4, push onto list */
        else if(strcmp(tok, "R4") == 0)
        {
            val.r = R4;
            t = new_token(linenum, val, REGISTER);
            push_token(t, tl);
        }

        /* Check for register R5, push onto list */
        else if(strcmp(tok, "R5") == 0)
        {
            val.r = R5;
            t = new_token(linenum, val, REGISTER);
            push_token(t, tl);
        }

        /* Check for register R6, push onto list */
        else if(strcmp(tok, "R6") == 0)
        {
            val.r = R6;
            t = new_token(linenum, val, REGISTER);
            push_token(t, tl);
        }

        /* Check for register R7, push onto list */
        else if(strcmp(tok, "R7") == 0)
        {
            val.r = R7;
            t = new_token(linenum, val, REGISTER);
            push_token(t, tl);
        }

        /* Check for register RC, push onto list */
        else if(strcmp(tok, "RC") == 0)
        {
            val.r = RC;
            t = new_token(linenum, val, REGISTER);
            push_token(t, tl);
        }

        /* Check for integer, push onto list */
        else if(is_int(tok) == 1)
        {
            sscanf(tok, "%d", &(val.i));
            t = new_token(linenum, val, INTEGER);
            push_token(t, tl);
        }

        /* Check for float, push onto list */
        else if(is_float(tok) == 1)
        {
            sscanf(tok, "%f", &(val.f));
            t = new_token(linenum, val, FLOAT);
            push_token(t, tl);
        }

        /* Check for char, push onto list */
        else if(is_char(tok, &ch) == 1)
        {
            val.c = ch;
            t = new_token(linenum, val, CHAR);
            push_token(t, tl);
        }

        /* Check for comment */
        else if(strcmp(tok, ";") == 0)
        {
            break;
        }

        /* Invalid token, output to err */
        else
        {
            fprintf(err, "INVALID TOKEN:  Line Number: %d\t\tToken: %s\n", linenum, tok);
            ++num_errors;
       }

        /* Get next token */
        tok = strtok(NULL, delims);
    }

    return num_errors;
}

/* 
 * Determines whether or not a string is an integer
 * Returns 1 if true, 0 if false 
 */
int is_int(char *tok)
{
    int i;

    /* Ensure that first character is either a negative sign or a digit */
    if(tok[0] == '-' || isdigit(tok[0]) != 0)
    {
        /* Check if every other character is a digit */
        for(i = 1; tok[i] != '\0'; ++i)
        {
            if(isdigit(tok[i]) == 0)
            {
                return 0;
            }
        }

        return 1;
    }
    else
    {
        return 0;
    }
}

/* 
 * Determines whether or not a string is a float
 * Returns 1 if true, 0 if false 
 */
int is_float(char *tok)
{
    int i;
    int dec = 0;

    /* Check if first character is a negative sign or a digit */
    if(tok[0] == '-' || isdigit(tok[0]) != 0)
    {
        /* Check if each subsequent character is a digit or decimal point */
        for(i = 1; tok[i] != '\0'; ++i)
        {
            /* Only one decimal point atlowed */
            if(tok[i] == '.')
            {
                ++dec;

                if(dec > 1)
                {
                    return 0;
                }
            }
            else if(tok[i] == 0)
            {
                return 0;
            }
        }

        return 1;
    }
    /* If first character is a decimal, check if next character is a digit */
    else if(tok[0] == '.' && isdigit(tok[1]) != 0)
    {
        ++dec;

        /* Check that every other character is a digit */
        for(i = 2; tok[i] != '\0'; ++i)
        {
            if(isdigit(tok[i]) == 0)
            {
                return 0;
            }
        }

        return 1;
    }
    else
    {
        return 0;
    }
}

/* 
 * Determines whether or not the token is a character to be used with PTC
 * Returns 1 if true (and puts the character in dest), 0 if false 
 */
int is_char(char *tok, char *dest)
{
    int i;
    int len = strlen(tok);

    /* Check for proper char syntax ('char' or '\char') and proper length */
    if(tok[0] == '\'')
    {
        /* escape sequences */
        if((len == 4) && ((tok[1] == '\\') && (tok[3] == '\'')))
        {
            if(tok[2] == 'n')
            {
                *dest = '\n';
                return 1;
            }
            else if(tok[2] == 't')
            {
                *dest = '\t';
                return 1;
            }
            else if(tok[2] == 's')
            {
                *dest = ' ';
                return 1;
            }
            else if(tok[2] == '\?')
            {
                *dest = '\?';
                return 1;
            }
            else if(tok[2] == '\\')
            {
                *dest = '\\';
                return 1;
            }
            else if(tok[2] == '\'')
            {
                *dest = '\'';
                return 1;
            }
            else if(tok[2] == '\"')
            {
                *dest = '\"';
                return 1;
            }
            else
                return 0;
        }
        /* normal character */
        else if((len == 3) && (tok[2] == '\''))
        {
            *dest = tok[1];
            return 1;
        }
        /* not a character */
        else
            return 0;
    }
    /* not a character */
    else
        return 0;
}
